<?php

abstract class FightPetAction
{
    abstract public function run(&$attacker, &$defender);
}

class PetNoAction extends FightPetAction
{
    public function run(&$attacker,&$defender)
    {
        return new FightPetActionResult('无行动');
    }
}

class PetDefendAction extends FightPetAction
{
    public function run(&$attacker,&$defender)
    {
        return new FightPetActionResult('防守',
            array(
                new FightPetActionEffect('self','defending','set',true),
            )
        );
    }
}

class PetPhysicalAttackAction extends FightPetAction
{
    public function run(&$attacker,&$defender)
    {
        $damage=0;
        if(!$this->defenderCanDodge($attacker, $defender)){
            // A普通攻击B，B受到的伤害值
            // 普通攻击伤害＝max（（攻击力－防御力）*2*random（0，20） / 100，10*random（0，100） / 100）
            $damage=(int)(max(($attacker->attack - $defender->defence)*2*mt_rand(0,20) /100, 10*mt_rand(0,100) /100));
            if($defender->defending)
                $damage=(int)($damage/4);
        }
        return new FightPetActionResult('攻击',
            array(
                new FightPetActionEffect('enemy','hp','change',-$damage)
            )
        );
    }
    
    private function defenderCanDodge(&$attacker, &$defender)
    {
        //A普通攻击B，判定B是否成功闪避。
        //闪避成功率=int（ln（B闪避-A命中）*10）
        //  当A命中大于B闪避时，LN函数无意义，则此时闪避率为0.
        $n = $defender->dodge - $attacker->hitRate;
        if($n<=0)
            return false;
        $dodgeRate = (int)(log($n)*10);
        return $dodgeRate < mt_rand(0,100) ? false : true;
    }    
}

class PetApplySkillAction extends FightPetAction
{
    private $skillName; 
    
    public function __construct($skillType) // attack or help
    {
        $this->skillName=$skillType.'Skills';
    }
    
    public function run(&$attacker,&$defender)
    {
        $skillName=$this->skillName;
        $skills=&$attacker->$skillName;
        $n=count($skills);
        if($n==0)
            return $this->runPhysicalAttack($attacker,$defender);

        $skillIds=array_keys($skills);    
        $skillId=$skillIds[mt_rand(0, $n-1)];
        $skill=PetSkill::loadPetSkill($skillId);
        $skill->level=$skills[$skillId];

        $actionResult=$skill->apply($attacker, $defender);
        if($actionResult===false) //must be mp not enough or level==0
            return $this->runPhysicalAttack($attacker,$defender);
        
        return $actionResult;
    }

    private function runPhysicalAttack(&$attacker,&$defender)
    {
        $action=new PetPhysicalAttackAction;
        return $action->run($attacker,&$defender);
    }    
}

class FightPetActionEffect
{
    public $target;
    public $attribute;
    public $changeOrSet;
    public $value;
    public $lastingRounds;
    public $visible;
    public function __construct($target, $attribute, $changeOrSet, $value, $lastingRounds=0, $visible=true)
    {
        $this->target=$target;
        $this->attribute=$attribute;
        $this->changeOrSet=$changeOrSet;
        $this->value=$value;
        $this->lastingRounds=$lastingRounds;
        $this->visible=$visible;
    }
}

class FightPetActionResult
{
    public $action;
    public $effects;
    public function __construct($action,$effects=array())
    {
        $this->action=$action;
        $this->effects=$effects;
    }
}

class FightPetResult
{
    public $selfOrEnemy;

    public $hpMaxBefore;
    public $hpBefore;
    public $mpMaxBefore;
    public $mpBefore;
    public $stateBefore;

    public $hpMaxAfter;
    public $hpAfter;
    public $mpMaxAfter;
    public $mpAfter;
    public $stateAfter;

    public $actionResults=array();
    
    private function addOneActionResult(&$actionResult)
    {
        foreach ($this->actionResults as $k=>&$result) {
            if ($result->action == $actionResult->action) {
                unset($this->actionResults[$k]);
                break;
            }
        }
        $this->actionResults[]=$actionResult;
    }

    public function addActionResult($actionResult)
    {
        if($actionResult) {
            if(is_array($actionResult)) {
                foreach ($actionResult as &$result) {
                    $this->addOneActionResult($result);
                }
            }
            else {
                $this->addOneActionResult($actionResult);
            } 
        }
    }
}

class FightRoundResult
{
    public $firstAttacker;
    public $secondAttacker;

    public function __construct()
    {
        $this->firstAttacker=new FightPetResult;
        $this->secondAttacker=new FightPetResult;
    }
}

class Fight
{
	const STORAGE_KEY='fight';

    public $id;
	public $self;
	public $enemy;
	public $location;
	public $lastRoundTime;
	public $valid=true;
    public $started=false;
    public $roundResults=array();
    public $awards=array();

    public function __sleep()
    {
        if(!$this->started)
            return array('self','enemy','location','lastRoundTime','valid','started');
        else
            return array('location','lastRoundTime','valid','started','roundResults', 'awards');
    }

	public static function loadFight()
	{
		return Yii::app()->user->getState(self::STORAGE_KEY);
	}

	public static function saveFight(&$fight)
	{
		return Yii::app()->user->setState(self::STORAGE_KEY, $fight);
	}

	public static function deleteFight()
	{
		Yii::app()->user->setState(self::STORAGE_KEY, null);
	}

	public function __construct($self, $enemy, $location)
	{
        $this->id=mt_rand();
		$this->self=$self;
		$this->enemy=$enemy;
		$this->location=$location;
		$this->lastRoundTime=time();
	}
    
    public function fightRound()
    {
        if(!$this->started) {
            $this->start();
            $this->started = true;
        }
        return array_shift($this->roundResults);
    }
    
    public function finished()
    {
        if(!$this->started)
            return false;
        else
            return empty($this->roundResults);
    }

    private function start()
    {
        $this->self->beforeFight();
        $this->enemy->beforeFight();
        $onePetDead=false;
        
        while(1){
            
            $this->self->beforeFightRound();
            $this->enemy->beforeFightRound();
            
            $roundResult = new FightRoundResult;
            $firstAttackerResult=&$roundResult->firstAttacker;
            $secondAttackerResult=&$roundResult->secondAttacker;
            
            //preprocess lasting effect
            $selfActionResult=$this->applyLastingEffects($this->self, $this->enemy);
            $enemyActionResult=$this->applyLastingEffects($this->enemy, $this->self);

            //attack seq
            if($this->compareAttackSpeed($this->self,$this->enemy)>0) {
                $firstAttacker=&$this->self;
                $firstAttackerResult->selfOrEnemy='self';
                $firstAttackerResult->addActionResult($selfActionResult);

                $secondAttacker=&$this->enemy;
                $secondAttackerResult->selfOrEnemy='enemy';
                $secondAttackerResult->addActionResult($enemyActionResult);
            }
            else {
                $firstAttacker=&$this->enemy;
                $firstAttackerResult->selfOrEnemy='enemy';
                $firstAttackerResult->addActionResult($enemyActionResult);
                
                $secondAttacker=&$this->self;
                $secondAttackerResult->selfOrEnemy='self';
                $secondAttackerResult->addActionResult($selfActionResult);
            }
            
            $firstAttackerResult->hpMaxBefore=$firstAttacker->hpMax;
            $firstAttackerResult->hpBefore=$firstAttacker->hp;
            $firstAttackerResult->mpMaxBefore=$firstAttacker->mpMax;
            $firstAttackerResult->mpBefore=$firstAttacker->mp;
            $firstAttackerResult->stateBefore=$firstAttacker->state;

            $secondAttackerResult->hpMaxBefore=$secondAttacker->hpMax;
            $secondAttackerResult->hpBefore=$secondAttacker->hp;
            $secondAttackerResult->mpMaxBefore=$secondAttacker->mpMax;
            $secondAttackerResult->mpBefore=$secondAttacker->mp;
            $secondAttackerResult->stateBefore=$secondAttacker->state;
            
            //first attack
            $firstAttackerResult->addActionResult($this->action($firstAttacker, $secondAttacker));
            $this->performActionResults($firstAttacker, $secondAttacker, $firstAttackerResult->actionResults);
            if(!$secondAttacker->alive()) { //second is dead
                $onePetDead=true;
            }

            if(!$onePetDead){
                //second attack
                $secondAttackerResult->addActionResult($this->action($secondAttacker, $firstAttacker));
                $this->performActionResults($secondAttacker, $firstAttacker, $secondAttackerResult->actionResults);
                if(!$firstAttacker->alive()) {
                    //first is dead
                    $onePetDead=true;
                }
            }
    
            $firstAttackerResult->hpMaxAfter=$firstAttacker->hpMax;
            $firstAttackerResult->hpAfter=$firstAttacker->hp;
            $firstAttackerResult->mpMaxAfter=$firstAttacker->mpMax;
            $firstAttackerResult->mpAfter=$firstAttacker->mp;
            $firstAttackerResult->stateAfter=$firstAttacker->state;
            
            $secondAttackerResult->hpMaxAfter=$secondAttacker->hpMax;
            $secondAttackerResult->hpAfter=$secondAttacker->hp;
            $secondAttackerResult->mpMaxAfter=$secondAttacker->mpMax;
            $secondAttackerResult->mpAfter=$secondAttacker->mp;
            $secondAttackerResult->stateAfter=$secondAttacker->state;

            $this->roundResults[]=$roundResult;
            if($onePetDead)
                break;
                
            $this->self->afterFightRound();
            $this->enemy->afterFightRound();            
        }

        $this->self->afterFight();
        $this->enemy->afterFight();        
    }
    
    private function compareAttackSpeed(&$aPet,&$bPet)
    {
        //A敏捷-B敏捷+ sign（B敏捷-A敏捷）*（（A敏捷+B敏捷）/2-(（A敏捷+B敏捷）/2-abs(A敏捷-B敏捷)/2)*rand(1,250)/100）
        //以上公式结果为正，则A优先攻击；为负，则B优先攻击。
        //Questions?
        $dexterityASubB = $aPet->dexterity - $bPet->dexterity;
        $dexterityAAddB = $aPet->dexterity + $bPet->dexterity;
        $sign = $dexterityASubB <= 0 ? 1 : -1;

        return $dexterityASubB + $sign * ($dexterityAAddB/2 - $dexterityAAddB/2 - abs($dexterityASubB)/2)*mt_rand(1,250)/100;
    }

    private function applyLastingEffects(&$self, &$enemy)
    {
        $lastEffects = $self->lastingEffects;
        foreach ($lastEffects as $k=>&$effects) {
            foreach ($effects as $id=>&$effect) {
                --$effect->lastingRounds;
                if ( $effect->attribute=='hp' && $effect->value<0 && $effect->target=='enemy' ) {
                    if (!isset($result))
                        $result = new FightPetActionResult($k);
                    $result->effects[] = $effect;
                }
                else {
                    if ($effect->target=='self')
                        $target = &$self;
                    else 
                        $target = &$enemy;
                    $method = $effect->changeOrSet.ucfirst($effect->attribute);
                    $target->$method($effect->value);
                }
                
                if($effect->lastingRounds == 0)
                    unset($effects[$id]);
            }
            
            if (empty($effects))
                unset($lastEffects[$k]);

            if (isset($result)) {
                if (!isset($results)) $results=array();
                $results[] = $result;
                unset($result);
            }
        }
        return $results;
    }

    private function calculateAction(&$attacker)
    {
        $physicalAttackRate = $attacker->physicalAttackRate;
        $skillAttackRate = $attacker->skillAttackRate;
        $skillHelpRate = $attacker->skillHelpRate;
        $defendRate = $attacker->defendRate;
        
        $totalRates = $physicalAttackRate + $skillAttackRate + $skillHelpRate + $defendRate;
               
        $skillAttackRate += $physicalAttackRate;
        $skillHelpRate += $skillAttackRate;
        $defendRate += $skillHelpRate;

        $rate = mt_rand(1,$totalRates);

        if($rate<=$physicalAttackRate)
            return new PetPhysicalAttackAction;
        if($rate<=$skillAttackRate)
            return new PetApplySkillAction('attack');
        if($rate<=$skillHelpRate)
            return new PetApplySkillAction('help');
        if($rate<=$defendRate)
            return new PetDefendAction();
        return new PetNoAction;    
    }

    // return action result
    private function action(&$attacker,&$defender)
    {
        return $this->calculateAction($attacker)->run($attacker,$defender);
    }
    
    private function calculateRaceFactor(&$attacker,&$defender)
    {
        switch ($attacker->race) {
        case 12:
            if ($defender->race == 15 || $defender->race == 16)
                return 125;
            break;
        case 13:
            if ($defender->race == 12 || $defender->race == 17)
                return 125;
            break;
        case 14:
            if ($defender->race == 13 || $defender->race == 15)
                return 125;
            break;
        case 15:
            if ($defender->race == 13 || $defender->race == 16)
                return 125;
            break;
        case 16:
            if ($defender->race == 14 || $defender->race == 17)
                return 125;
            break;
        case 17:
            if ($defender->race == 12 || $defender->race == 14)
                return 125;
            break;
        }
        return 100;
    }
    
    private function removeLastingEffects(&$pet, $target, $attr, $op)
    {    
        $lastEffects = &$pet->lastingEffects;
        
        foreach ( $lastEffects as $k=>&$effects) {
            foreach ( $effects as &$effect) {
                if ($effect->target==$target && $effect->attribute==$attr && $effect->changeOrSet == $op) {
                    unset($lastEffects[$k]);
                    if (empty($effects))
                        unset($lastEffects[$k]);
                    return true;
                }
            }
        }
        return false;
    }

    // calculate final damage and then apply
    private function performActionResults(&$attacker,&$defender,&$actionResults)
    {
        foreach ($actionResults as &$action) {
        
            $lastingEffects = array();

            foreach ($action->effects as &$effect) {

                if( $effect->target == 'self' ) {
                    $target = &$attacker;
                    if ($effect->attribute=='state' && $target->state != 'normal') {
                        if(!$this->removeLastingEffects($attacker, 'self', 'state', 'set'))
                            $this->removeLastingEffects($defender, 'enemy', 'state', 'set');
                        $target->afterFightRound();
                    }
                }
                else {
                    $target = &$defender;
                    if ($effect->attribute=='state' && $target->state != 'normal') {
                        if(!$this->removeLastingEffects($attacker, 'enemy', 'state', 'set'))
                            $this->removeLastingEffects($defender, 'self', 'state', 'set');
                        $target->afterFightRound();
                    }
                    elseif ($effect->attribute=='hp' && $effect->value<0) {
                        $effect->value = (int)($effect->value/100 * $this->calculateRaceFactor($attacker, $defender));
                    }                
                }
                
                if ($effect->lastingRounds) {
                    $lastingEffects[] = clone $effect;
                }
                                
                $method = $effect->changeOrSet.ucfirst($effect->attribute);
                $target->$method($effect->value);
            }
            
            if (!empty($lastingEffects)) {
                $attacker->lastingEffects[$action->action] = $lastingEffects;
            }
        }
    }
}

?>
